﻿using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System;
using System.Windows.Media;
using System.IO;
using System.Text;
using System.Xml;
using System.Windows.Markup;
using System.Diagnostics;

namespace BinaryXmlInspector
{
    public partial class BinaryXmlViewer : UserControl
    {
        public BinaryXmlViewer()
        {
            InitializeComponent();
            VM = new BinaryViewModel( s => { XmlText = s; } );
            PopulateSyntaxColors();
        }

        public BinaryViewModel VM;

        public override void OnApplyTemplate()
        {
            VM.Pretty = true;
            VM.Explode = false; 
            this.DataContext = VM;      

            base.OnApplyTemplate();
        }
        
        
#region XmlView rich text box

        /// <summary>
        /// Main event handler for syntax highlighting.
        /// </summary>
        private void XmlChanged(object sender, TextChangedEventArgs e)
        {
            VM.Dirty = true;
            if (VM.Pretty)
                Render(XmlText);
        }

        private string XmlText
        {
            get { return EntireTextRange.Text; }
            set { EntireTextRange.Text = value; }
        }

        private TextRange EntireTextRange
        {
            get { return new TextRange(XmlView.Document.ContentStart, XmlView.Document.ContentEnd); }
        }

        private void Render(string text)
        {
            // turn off event firing while we modify internals
            XmlView.TextChanged -= this.XmlChanged;

            // save the user's cursor position (in actual text characters; the pointer itself will be invalidated, and even the # of
            // logical symbols is likely to change when we re-parse)
            var caret = new TextRange(XmlView.Document.ContentStart, XmlView.CaretPosition).Text.Length;
                        
            if (VM.Pretty)
            {
                var newTokens = tokenizer.Tokenize(text);
                Format(EntireTextRange, newTokens);
            }
            else
            {                
                XmlText = "";                
                XmlText = text;
            }

            XmlView.CaretPosition = XmlView.Document.ContentStart;
            //XmlView.CaretPosition.Mo

            XmlView.TextChanged += this.XmlChanged;
        }

        private void Format(TextRange tr, List<Token> tokens)
        {                           
            var xaml = new StringBuilder();
            xaml.Append(@"
<Section
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Paragraph xml:space='preserve'>");
            foreach (var token in tokens)
            {
                if (token.Type == TokenType.Whitespace)
                {
                    xaml.Append(token.Value);
                    continue;
                }
                xaml.Append("<Run Foreground='");
                xaml.Append(m_syntaxColors[token.Type]);
                xaml.Append("'>");
                xaml.Append(token.Value.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;"));
                xaml.Append("</Run>");
            }
            xaml.Append("</Paragraph></Section>");

            var buf = Encoding.UTF8.GetBytes(xaml.ToString());
            try
            {
                tr.Load(new MemoryStream(buf), DataFormats.Xaml);
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.ToString());
            }
        }

        private Dictionary<TokenType, Brush> m_syntaxColors = new Dictionary<TokenType, Brush>();
        private XmlTokenizer tokenizer = new XmlTokenizer();

        private void PopulateSyntaxColors()
        {
            m_syntaxColors.Add(TokenType.AttributeBoundary, new SolidColorBrush(Colors.Green));
            m_syntaxColors.Add(TokenType.AttributeEquals,   new SolidColorBrush(Colors.Black));
            m_syntaxColors.Add(TokenType.AttributeName,     new SolidColorBrush(Colors.Red));
            m_syntaxColors.Add(TokenType.AttributeValue,    new SolidColorBrush(Colors.MediumBlue));
            m_syntaxColors.Add(TokenType.Comment,           new SolidColorBrush(Colors.MediumSeaGreen));
            m_syntaxColors.Add(TokenType.ElementBoundary,   new SolidColorBrush(Colors.Blue));
            m_syntaxColors.Add(TokenType.ElementName,       new SolidColorBrush(Colors.Brown));
            m_syntaxColors.Add(TokenType.InnerText,         new SolidColorBrush(Colors.Black));
            m_syntaxColors.Add(TokenType.Whitespace,        new SolidColorBrush(Colors.Black));  // shouldn't matter
        }

#endregion

        private void SearchKeyPress(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                SearchNext();
            }
        }

        private void SearchNext()
        {
            // TODO
        }

        private void OpenClick(object sender, RoutedEventArgs e)
        {
            // TODO
        }

        private void OpenWithClick(object sender, RoutedEventArgs e)
        {
            // TODO
        }

        private void PrettyClick(object sender, RoutedEventArgs e)
        {
            // don't update the text, just reformat it
            Render(XmlText);
        }
                
        private void ExplodeClick(object sender, RoutedEventArgs e)
        {
            // try to transcode the current contents of the textbox to/from binary XML so it's passed thru the ViewModel's 
            // customized XmlWriter again
            var transcoded = VM.DecodeBinaryXML(VM.EncodeBinaryXML(XmlText));
            // render the re-formatted text, but only the transcode was successful
            if (transcoded != "")
            {                
                Render(transcoded);
            }

            // toggle scrolling
            // TODO: make this a style, and turn off justified spacing when not exploded
            XmlView.Document.PageWidth = VM.Explode ? 10000 : Double.NaN;
        }

        public void ShowAboutBox()
        {
            // TODO
        }
    }
}
